package org.easy.tool.common;

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;

import java.sql.*;
import java.util.*;

/**
 * @author Valley
 * Created by Valley on 2023/7/14 16:19
 */
public class DBUtil {

    //统一管理数据连接的管理器
    public static LinkedHashMap<String, DruidDataSource> dsManager = new LinkedHashMap<>();
    //统一手动实现数据库连接池
    public static LinkedHashMap<String, MyDataSource> connManager = new LinkedHashMap<>();
    //默认配置项
    public static String DEFAULT_DRIVER = "com.mysql.jdbc.Driver";
    public static String DEFAULT_URL = "jdbc:mysql://localhost:3306/learn?characterEncoding=UTF-8&useUnicode=true&useSSL=false";
    public static String DEFAULT_USERNAME = "root";
    public static String DEFAULT_PASSWORD = "123456";
    public static long DEFAULT_TIMEOUT = 3 * 60 * 1000;  //3分钟
    public static long DEFAULT_DESTROY = 30 * 60 * 1000;  //30分钟

    /**
     * 功能：往数据连接管理器里加入连接
     */
    public static void addConn(String name, Connection connection) {
        MyDataSource ds = new MyDataSource();
        ds.setActiveTime(TimeUtil.nowTimeStamp());
        ds.setConnection(connection);
        connManager.put(name, ds);
    }

    /**
     * 功能：从数据连接管理器里取连接
     */
    public static Connection getConn(String name, String url) throws Exception{
        MyDataSource ds = connManager.get(name);
        if (ds != null){
            ds.setActiveTime(TimeUtil.nowTimeStamp());
            return ds.getConnection();
        }else {
            Connection conn = createConn(url, "", "");
            addConn(name, conn);
            return conn;
        }
    }

    /**
     * 功能：从数据连接管理器里删除不活跃连接
     */
    public static void removeConn() {
        connManager.keySet().forEach(s->{
            MyDataSource dataSource = connManager.get(s);
            long timeout = TimeUtil.nowTimeStamp() - dataSource.getActiveTime();
            if (timeout > DEFAULT_DESTROY){
                MyDataSource remove = connManager.remove(s);
                try {
                    remove.getConnection().close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }

    /**
     * 功能：往数据连接管理器里新增数据连接连接池
     */
    public static void addDS(String name, DruidDataSource dataSource) {
        dsManager.put(name, dataSource);
    }

    /**
     * 功能：从数据连接管理器根据名称获取数据连接
     */
    public static DruidDataSource getDS(String name) throws Exception{
        DruidDataSource dataSource = dsManager.get(name);
        if (dataSource == null || dataSource.isClosed()){
            DruidDataSource druidMySQL = createDruidDS();
            addDS(name, druidMySQL);
            return druidMySQL;
        }
        return dsManager.get(name);
    }



    /**
     * 功能：创建Druid数据连接源，设置最大存活时间，大于30000ms
     * url：数据库的JDBC连接URL。
     * username：连接数据库的用户名。
     * password：连接数据库的密码。
     * driverClassName：数据库驱动类的名称。
     * minIdle：连接池中保持的最小空闲连接数。
     * maxActive：连接池中可以同时存在的最大活跃连接数。
     * maxWait：连接池中的线程等待获取可用连接的最大时间，单位为毫秒。
     * timeBetweenEvictionRunsMillis：定期检测连接闲置时间，以便回收空闲连接，单位为毫秒。
     * minEvictableIdleTimeMillis：连接空闲超过指定时间后被认为是不需要的，可以被释放，单位为毫秒。
     * validationQuery：用于验证连接是否有效的SQL查询语句。
     * testWhileIdle：在连接闲置时是否执行测试查询来检查连接是否有效。
     * testOnBorrow：在从连接池获取连接时是否执行测试查询来检查连接是否有效。
     * testOnReturn：在将连接返回到连接池时是否执行测试查询来检查连接是否有效。
     * poolPreparedStatements：是否启用预编译语句池，以提高性能。
     * maxPoolPreparedStatementPerConnectionSize：每个连接上预编译语句池的最大大小。
     */
    public static DruidDataSource createDruidDS(String driver, String url, String username, String password, long keepAlive) throws Exception {
        DruidDataSource dataSource = new DruidDataSource();
        Class.forName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setMinEvictableIdleTimeMillis(keepAlive);
        dataSource.setMaxActive(3);
        dataSource.setMaxWait(keepAlive);
        return dataSource;
    }

    /**
     * 功能：创建Druid数据连接，默认是mysql连接
     */
    public static DruidDataSource createDruidDS() throws Exception {
        if (dsManager.keySet().size() > 10){
            removeDS();
        }
        return createDruidDS(DEFAULT_DRIVER, DEFAULT_URL, DEFAULT_USERNAME, DEFAULT_PASSWORD, DEFAULT_TIMEOUT);
    }

    /**
     * 功能：删除已超时数据连接源
     */
    public static void removeDS() {
        dsManager.keySet().forEach(s->{
            DruidDataSource dataSource = dsManager.get(s);
            long span = TimeUtil.nowTimeStamp() - dataSource.getCreatedTime().getTime();
            if (span > DEFAULT_DESTROY){
                DruidDataSource remove = dsManager.remove(s);
                remove.close();
            }
        });
    }

    /**
     * 功能：获取数据库连接
     */
    public static Connection createConn(String url, String username, String password) throws Exception {
        Class.forName(DEFAULT_DRIVER);
        return DriverManager.getConnection(url, username, password);
    }

    /**
     * 功能：使用数据库连接sql，获取指定Sql执行结果
     */
    public static List<Object> execute(String url, String username, String password, String sql) {
        List<Object> list = new ArrayList<>();
        try {
            Connection conn = createConn(url, username, password);
            PreparedStatement prepareStatement = conn.prepareStatement(sql);
            ResultSet resultSet = prepareStatement.executeQuery();
            assert resultSet != null;
            int columnCount = resultSet.getMetaData().getColumnCount();
            while (resultSet.next()) {
                for (int i = 1; i <= columnCount; i++) {
                    Object object = resultSet.getObject(i);
                    list.add(object);
                }
            }
            resultSet.close();
            prepareStatement.close();
            conn.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

    /**
     * 功能：statement执行sql获得执行结果
     */
    public static List<String> executeSql(Connection conn, String sql) throws Exception {
        List<String> tableList = new ArrayList<>();
        PreparedStatement prepareStatement = conn.prepareStatement(sql);
        ResultSet resultSet = prepareStatement.executeQuery();
        assert resultSet != null;
        int columnCount = resultSet.getMetaData().getColumnCount();
        while (resultSet.next()) {
            for (int i = 1; i <= columnCount; i++) {
                String tableName = resultSet.getString(i);
                tableList.add(tableName);
            }
        }
        resultSet.close();
        prepareStatement.close();
        return tableList;
    }

    /**
     * 功能：获取数据库所有表名,dbName为null默认当前连接数据库
     */
    public static List<String> getTable(String url, String username, String password, String dbName) {
        List<String> tableList = new ArrayList<>();
        try {
            Connection connection = createConn(url, username, password);
            DatabaseMetaData meta = connection.getMetaData();
            ResultSet rs = meta.getTables(dbName, null, null,
                    new String[]{"TABLE"});
            while (rs.next()) {
                String tableName = rs.getString(3);
                String user = rs.getString(2);
                tableList.add(tableName);
            }
            rs.close();
            connection.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return tableList;
    }

    /**
     * 功能：生成常用jdbc连接url
     */
    public static String url() {
        return "jdbc:mysql://localhost:3306/learn?characterEncoding=UTF-8&useUnicode=true&useSSL=false";
    }

    /**
     * 功能：生成常用jdbc连接url
     */
    public static String url(String address) {
        return "jdbc:mysql://" + address + "?characterEncoding=UTF-8&useUnicode=true&useSSL=false";
    }

    /**
     * 功能：借助MybatisPlus自动生成代码，默认是MYSQL，参数是表名数组
     * 使用的地址是数据库地址：127.0.0.1:3306/test
     */
    public static boolean generateMySQL(String address, String username, String password, String packageName, List<String> tableNames, String pre) {
        boolean res = true;
        for (String tableName : tableNames) {
            String url = url(address);
            res = res && generateCode(url, username, password, packageName, tableName, pre);
        }
        return res;
    }

    /**
     * 功能：借助MybatisPlus自动生成代码，参数是单个表名
     */
    public static boolean generateCode(String url, String username, String password, String packageName, String tableName, String pre) {
        try {
            FastAutoGenerator.create(url, username, password)
                    // 全局配置
                    .globalConfig(global -> {
                        global.author("Valley") // 设置作者
                                .commentDate("yyyy-MM-dd HH:mm:ss")   //注释日期
                                .outputDir(System.getProperty("user.dir") + "/src/main/java") // 指定输出目录
                                .disableOpenDir() //禁止打开输出目录，默认打开
                        ;
                    })
                    // 包配置
                    .packageConfig(pack -> {
                        pack.parent(packageName) // 设置父包名
                                .pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "/src/main/resources/mapper")); // 设置mapperXml生成路径
                    })
                    // 策略配置
                    .strategyConfig(strategy -> {
                        strategy.addInclude(tableName) // 设置需要生成的表名
                                .addTablePrefix(pre) // 设置过滤表前缀
                                // Entity 策略配置
                                .entityBuilder()
                                .enableLombok() //开启 Lombok
//                                .enableFileOverride() // 覆盖已生成文件
                                .naming(NamingStrategy.underline_to_camel)  //数据库表映射到实体的命名策略：下划线转驼峰命
                                .columnNaming(NamingStrategy.underline_to_camel)    //数据库表字段映射到实体的命名策略：下划线转驼峰命
                                // Mapper 策略配置
                                .mapperBuilder()
//                                .enableFileOverride() // 覆盖已生成文件
                                // Service 策略配置
                                .serviceBuilder()
//                                .enableFileOverride() // 覆盖已生成文件
                                .formatServiceFileName("I%s") //格式化 service 接口文件名称，%s进行匹配表名，如 IUser
                                .formatServiceImplFileName("%sService") //格式化 service 实现类文件名称，%s进行匹配表名，如 UserService
                                // Controller 策略配置
                                .controllerBuilder()
//                                .enableFileOverride() // 覆盖已生成文件
                        ;
                    })
                    .execute();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


}
